package com.firefly.netty.demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.firefly.dao.util.RedisUtils;
import com.firefly.util.utils.JsonUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @description:
 * @author: lzg(萤火科技 ： 854378082 @ qq.com)
 * @date: 2022/9/14
 **/

@Component
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {

    @Autowired
    private RedisUtils redisUtils;


    //2.声明本类
    private static NettyServerHandler nettyServerHandler;

    //3.声明构造方案
    public NettyServerHandler() {
    }

    @PostConstruct
    public void init() {
        nettyServerHandler = this;
        nettyServerHandler.redisUtils = this.redisUtils;
    }

    //定义一个channle 组，管理所有的channel
    //GlobalEventExecutor.INSTANCE) 是全局的事件执行器，是一个单例
    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 有客户端与服务器发生连接时执行此方法
     * 1.打印提示信息
     * 2.将客户端保存到 channelGroup 中
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        log.debug("new client. address: " + channel.remoteAddress());
        channelGroup.add(channel);
    }

    /**
     * 当有客户端与服务器断开连接时执行此方法，此时会自动将此客户端从 channelGroup 中移除
     * 1.打印提示信息
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        log.debug("remove client, address: " + channel.remoteAddress());
        removeRedis(channel);
    }

    /**
     * 表示channel 处于活动状态
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug(ctx.channel().remoteAddress() + " activing");
    }

    /**
     * 表示channel 处于不活动状态
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.debug(ctx.channel().remoteAddress() + " not active");
    }

    /**
     * 读取到客户端发来的数据数据
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws JsonProcessingException {
        //获取到当前channel
        Channel channel = ctx.channel();
        log.debug("request address: " + channel.remoteAddress() + ",content: " + msg);
        StringBuilder sb = null;
        DataFrame request = null;
        // 报文解析处理
        // {'Len':'0124','Cmd':'Set','SN':'2','DataTime':'220727162710','CRC':'FFFF','DataValue':{'04A20102':'731C6885#C390','04A20105':'01'}}
        request = JsonUtil.str2Obj(msg, DataFrame.class);
        // 返回客户端数据
        DataFrame response = doBusiness(request, channel);
        log.debug("response----------------" + response);
        switch (request.getCmd()) {
            // 登录,主动上报,心跳
            case "Login":
                ctx.writeAndFlush(response.toString());
                break;
            default:
                // 不认识的通道直接关闭
                ctx.close();
                break;
        }
    }

    // 处理自己的业务
    private DataFrame doBusiness(DataFrame dataFrame, Channel channel) {
        String deviceCode = dataFrame.getAddr();
        // log.debug("doBusiness---------------" + deviceCode);
        DataFrame result = new DataFrame();
        Map dataValue = new HashMap<>();
        result.setCmd(dataFrame.getCmd());
        result.setSN(dataFrame.getSN());
        result.setAddr(deviceCode);
        result.setDataTime(dataFrame.getDataTime());
        // 具体业务走哪里
        switch (dataFrame.getCmd()) {
            // 登录
            case "Login":
                dataValue = dataFrame.getDataValue();
                deviceLogin(deviceCode, channel);
                break;
            default:
                break;
        }
        result.setDataValue(dataValue);
        return result;
    }

    // 登录
    private void deviceLogin(String deviceCode, Channel channel) {
        // 取前12个字符,放入redis
        nettyServerHandler.redisUtils.hset("channel-code", channel.id().asLongText(), deviceCode);
        nettyServerHandler.redisUtils.hset("code-online", deviceCode, channel.id());
    }

    // 存储client的ctx并获取device的ctx
    private Channel getDeviceCtx(String deviceCode,Channel channel){
        // 找到对应的ctx
        ChannelId channelId = (ChannelId) nettyServerHandler.redisUtils.hget("code-online",deviceCode);
        if (channelId == null){
            throw new RuntimeException("not device tcp client");
        }
        // 存储
        nettyServerHandler.redisUtils.hset("channel-client",channel.id().asLongText(),deviceCode);
        nettyServerHandler.redisUtils.hset("code-client", deviceCode,channel.id());
        return channelGroup.find(channelId);
    }
    // 获取client的ctx
    private Channel getClientCtx(Channel channel){
        String deviceCode = (String) nettyServerHandler.redisUtils.hget("channel-code",channel.id().asLongText());
        if (deviceCode == null){
            throw new RuntimeException("not device tcp client");
        }
        ChannelId channelId = (ChannelId) nettyServerHandler.redisUtils.hget("code-client",deviceCode);
        if (channelId == null){
            throw new RuntimeException("not client request");
        }
        return channelGroup.find(channelId);
    }
    // 断开连接时删除redis数据
    private void removeRedis(Channel channel){
        // 离线，要判断是设备还是client断开
        Object clientDeviceCode = nettyServerHandler.redisUtils.hget("channel-client",channel.id().asLongText());
        if (clientDeviceCode != null) {
            nettyServerHandler.redisUtils.hdel("channel-client",channel.id().asLongText());
            nettyServerHandler.redisUtils.hdel("code-client",clientDeviceCode);
        }else{
            Object deviceCode = nettyServerHandler.redisUtils.hget("channel-code",channel.id().asLongText());
            if (deviceCode != null){
                nettyServerHandler.redisUtils.hdel("channel-code", channel.id().asLongText());
                nettyServerHandler.redisUtils.hdel("code-online", deviceCode);
            }
        }
        channelGroup.remove(channel);
    }
    /**
     * 处理异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        String msg = cause.getMessage();
        // 有时候不需要响应
        String errorCode = "{'error':'" + msg + "'}";
        ctx.writeAndFlush(errorCode);
        //关闭通道
        ctx.close();
        removeRedis(ctx.channel());
        log.error("netty exceptionCaught: " + cause);
    }
}